// @flow

/**
 * 定义类方法
 * 
 * @param {any} prototype 
 * @param {any} funs 
 */
export function defineFunction(prototype, funs) {
  for (var fn in funs) {
    prototype[fn] = funs[fn];
  }
}

export function extend (dest, ...sources) {
  for (const src of sources) {
      for (const k in src) {
          dest[k] = src[k];
      }
  }
  return dest;
}

export function endsWith(string, suffix) {
  return string.indexOf(suffix, string.length - suffix.length) !== -1;
}

//type Listener = (Object) => any;
//type Listeners = { [string]: Array<Listener> };

function _addEventListener(type, listener, listenerList) {
  listenerList[type] = listenerList[type] || [];
  listenerList[type].push(listener);

}

function _removeEventListener(type, listener, listenerList) {
  if (listenerList && listenerList[type]) {
    const index = listenerList[type].indexOf(listener);
    if (index !== -1) {
      listenerList[type].splice(index, 1);
    }
  }
}

/**
 * Methods mixed in to other classes for event capabilities.
 *
 * @mixin Evented
 */
function Evented() {

}

Evented._listeners = [];
Evented._oneTimeListeners = [];
Evented._eventedParent = new Evented();
Evented._eventedParentData = {};

defineFunction(Evented.prototype, {
  /**
   * Adds a listener to a specified event type.
   *
   * @param {string} type The event type to add a listen for.
   * @param {Function} listener The function to be called when the event is fired.
   *   The listener function is called with the data object passed to `fire`,
   *   extended with `target` and `type` properties.
   * @returns {Object} `this`
   */
  on(type, listener) {
    this._listeners = this._listeners || {};
    _addEventListener(type, listener, this._listeners);

    return this;
  },

  /**
   * Removes a previously registered event listener.
   *
   * @param {string} type The event type to remove listeners for.
   * @param {Function} listener The listener function to remove.
   * @returns {Object} `this`
   */
  off(type, listener) {
    _removeEventListener(type, listener, this._listeners);
    _removeEventListener(type, listener, this._oneTimeListeners);

    return this;
  },

  /**
   * Adds a listener that will be called only once to a specified event type.
   *
   * The listener will be called first time the event fires after the listener is registered.
   *
   * @param {string} type The event type to listen for.
   * @param {Function} listener The function to be called when the event is fired the first time.
   * @returns {Object} `this`
   */
  once(type, listener) {
    this._oneTimeListeners = this._oneTimeListeners || {};
    _addEventListener(type, listener, this._oneTimeListeners);

    return this;
  },

  /**
   * Fires an event of the specified type.
   *
   * @param {string} type The type of event to fire.
   * @param {Object} [data] Data to be passed to any listeners.
   * @returns {Object} `this`
   */
  fire(type, data) {
    if (this.listens(type)) {
      data = extend({}, data, {
        type: type,
        target: this
      });

      // make sure adding or removing listeners inside other listeners won't cause an infinite loop
      const listeners = this._listeners && this._listeners[type] ? this._listeners[type].slice() : [];

      for (let i = 0; i < listeners.length; i++) {
        listeners[i].call(this, data);
      }

      const oneTimeListeners = this._oneTimeListeners && this._oneTimeListeners[type] ? this._oneTimeListeners[type].slice() : [];

      for (let i = 0; i < oneTimeListeners.length; i++) {
        oneTimeListeners[i].call(this, data);
        _removeEventListener(type, oneTimeListeners[i], this._oneTimeListeners);
      }

      if (this._eventedParent) {
        this._eventedParent.fire(type, extend({}, data, typeof this._eventedParentData === 'function' ? this._eventedParentData() : this._eventedParentData));
      }

      // To ensure that no error events are dropped, print them to the
      // console if they have no listeners.
    } else if (endsWith(type, 'error')) {
      console.error((data && data.error) || data || 'Empty error event');
    }

    return this;
  },

  /**
   * Returns a true if this instance of Evented or any forwardeed instances of Evented have a listener for the specified type.
   *
   * @param {string} type The event type
   * @returns {boolean} `true` if there is at least one registered listener for specified event type, `false` otherwise
   */
  listens(type) {
    return (
      (this._listeners && this._listeners[type] && this._listeners[type].length > 0) ||
      (this._oneTimeListeners && this._oneTimeListeners[type] && this._oneTimeListeners[type].length > 0) ||
      (this._eventedParent && this._eventedParent.listens(type))
    );
  },

  /**
   * Bubble all events fired by this instance of Evented to this parent instance of Evented.
   *
   * @private
   * @returns {Object} `this`
   */
  setEventedParent(parent, data) {
    this._eventedParent = parent;
    this._eventedParentData = data;

    return this;
  }
});
export default Evented;
